package demo3

import (
	"fmt"
	"runtime"
)

/*
	recover 内置函数
		1. 一般情况下，recover 应该在一个使用 defer 关键字的函数中执行以有效截取错误处理流程。
		2. 如果在 deferred 函数中调用了内置函数 recover，并且定义该 defer 语句的函数发生了 panic 异常，
			recover 会使程序从 panic 中恢复，并返回 panic value。
			导致 panic 异常的函数不会继续运行，但能正常返回。在未发生 panic 时调用 recover，recover 会返回 nil。
		3. Go 没有异常系统，其使用 panic 触发宕机类似于其他语言的抛出异常，那么 recover 的宕机恢复机制就对应 try/catch 机制。
		4. 利用recover处理panic指令，defer必须在panic之前声明，否则当panic时，recover无法捕获到panic．
*/

// demo1
// panic 时传递的上下文
type panicContext struct {
	function string // 所在函数
}

// 保护方式允许一个函数
func protectRun(entry func()) {
	// 延迟处理的函数
	defer func() {

		// 发生宕机时，获取 panic 传递的上下文
		err := recover()

		switch err.(type) {
		case runtime.Error: // 允许时错误
			fmt.Println("runtime error:", err)
		default: // 非运行时错误
			fmt.Println("error:", err)
		}
	}()

	entry()
}

// TestDemo39 recover 使程序从 panic 中恢复
func TestDemo39() {
	// demo1： 保护 panic 执行的函数 protectRun
	fmt.Println("运行前")

	// 允许一段手动触发的错误
	protectRun(func() {
		fmt.Println("手动 panic 前")

		// 使用 panic 传递上下文
		panic(&panicContext{
			"手动触发panic",
		})

		// fmt.Println("手动 panic 后")
	})

	// 故意造成空指针访问错误
	protectRun(func() {
		fmt.Println("赋值宕机前")

		var a *int
		*a = 1

		fmt.Println("赋值宕机后")
	})

	fmt.Println("运行后")

	// demo2: 利用recover处理panic指令，defer必须在panic之前声明，否则当panic时，recover无法捕获到panic．
	fmt.Println("c")
	defer func() {
		fmt.Println("d")
		if err := recover(); err != nil {
			// err 就是 panic 传入的内容
			fmt.Println(err)
		}
		fmt.Println("e")
	}()

	// 调用 f
	f()
	fmt.Println("f") // 这里下面的代码不会执行
}

// demo2
func f() {
	fmt.Println("a")

	panic("错误信息")

	// fmt.Println("b") // 从这里开始下面的代码不会执行
}

/*
	tips:
	panic 和 defer 的组合有如下特性：
		1.有 panic 没 recover，程序宕机。
		2. 有 panic 也有 recover 捕获，程序不会宕机。执行完对应的 defer 后，从宕机点退出当前函数后继续执行。

		虽然 panic/recover 能模拟其他语言的异常机制，但并不建议代表编写普通函数也经常性使用这种特性。

		在 panic 触发的 defer 函数内，可以继续调用 panic，进一步将错误外抛直到程序整体崩溃。

		如果想在捕获错误时设置当前函数的返回值，可以对返回值使用命名返回值方式直接进行设置。
*/
